﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
using System.Web.Http.Dispatcher;
using System.Web.Routing;
using CafeOnline.MvcFramework.Composition;
using CafeOnline.MvcFramework.Configuration;
using CafeOnline.MvcFramework.Interfaces;
using CafeOnline.MvcFramework.Extensions;
using System.Web.Mvc;
using CafeOnline.MvcFramework.Exceptions;
using System.IO;

namespace CafeOnline.MvcFramework.Web {
    public class MefMvcApplication : HttpApplication {
        private static CompositionContainer m_currentCompositionContainner;
        public static CompositionContainer CurrentContainer {
            get {
                return m_currentCompositionContainner;
            }
        }
        #region Fields
#pragma warning disable 649
        [ImportMany]
        private IEnumerable<Lazy<IRouteRegistrar, IRouteRegistrarMetadata>> RouteRegistrars;
        [ImportMany]
        private IEnumerable<Lazy<IWebApiRouteRegistrar, IRouteRegistrarMetadata>> WebApiRoutesRegistrars;
        
        private static IEnumerable<Lazy<IActionVerb, IActionVerbMetadata>> ActionVerbs;

        [Import]
        private ImportControllerFactory ControllerFactory;
#pragma warning restore 649
        #endregion

        #region Methods
        /// <summary>
        /// The start method of the application.
        /// </summary>
        protected virtual void Application_Start() {
            // Perform any tasks required before composition.
            PreCompose();

            // Compose the application.
            Compose();
            //GlobalConfiguration.Configuration.DependencyResolver = 
            // Set the controller factory.
            ControllerBuilder.Current.SetControllerFactory(ControllerFactory);
            // Set the view engine that supports imported areas.
            ViewEngines.Engines.Add(new AreaViewEngine());

            // Initialises the application.
            Initialise();

            // Register MVC routes.
            RegisterRoutes();
        }

        /// <summary>
        /// Initialises the application.
        /// </summary>
        protected virtual void Initialise() { }

        /// <summary>
        /// Creates the composition container.
        /// </summary>
        /// <returns></returns>
        protected virtual CompositionContainer CreateCompositionContainer() {
            var catalog = new AggregateCatalog();
            catalog.Catalogs.Add(new DirectoryCatalog(MapPath("~/bin")));

            var config = CompositionConfigurationSection.GetInstance();
            if (config != null && config.Catalogs != null) {
                config.Catalogs
                    .Cast<CatalogConfigurationElement>()
                    .ForEach(c => {
                        if (!string.IsNullOrEmpty(c.Path)) {
                            string path = c.Path;
                            if (path.StartsWith("~"))
                                path = MapPath(path);

                            foreach (var directoryCatalog in GetDirectoryCatalogs(path))
                                catalog.Catalogs.Add(directoryCatalog);
                        }
                    });
            }

            var provider = new DynamicInstantiationExportProvider();
            var container = new CompositionContainer(catalog, provider);
            provider.SourceProvider = container;

            return container;
        }

        /// <summary>
        /// Fired before the application is composed.
        /// </summary>
        protected virtual void PreCompose() { }

        /// <summary>
        /// Composes the application.
        /// </summary>
        protected virtual void Compose() {
            var container = CreateCompositionContainer();
            if (container == null)
                return;

            container.ComposeParts(this);
            ActionVerbs = container.GetExports<IActionVerb, IActionVerbMetadata>();
            var webApiRoute = System.Web.Http.GlobalConfiguration.Configuration;
            webApiRoute.DependencyResolver =
                new MefMvcDependencyResolver(container);
            m_currentCompositionContainner = container;
        }

        /// <summary>
        /// Registers any routes required by the application.
        /// </summary>
        protected virtual void RegisterRoutes() {
            if (WebApiRoutesRegistrars != null && WebApiRoutesRegistrars.Count() > 0) {
                var webApiRoute = System.Web.Http.GlobalConfiguration.Configuration;
                
                var registrars = WebApiRoutesRegistrars
                    .OrderBy(lazy => lazy.Metadata.Order)
                    .Select(lazy => lazy.Value);

                registrars.ForEach(r => r.RegisterRoutes(webApiRoute));
            }

            if (RouteRegistrars != null && RouteRegistrars.Count() > 0) {
                var routes = RouteTable.Routes;

                var registrars = RouteRegistrars
                    .OrderBy(lazy => lazy.Metadata.Order)
                    .Select(lazy => lazy.Value);

                registrars.ForEach(r => r.RegisterIgnoreRoutes(routes));
                registrars.ForEach(r => r.RegisterRoutes(routes));    
            }
            
            
        }

        /// <summary>
        /// Maps the specified virtual path.
        /// </summary>
        /// <param name="virtualPath">The virtual path to map.</param>
        /// <returns>The specified virtual path as a mapped path.</returns>
        protected string MapPath(string virtualPath) {
            Throw.IfArgumentNullOrEmpty(virtualPath, "virtualPath");

            return HttpContext.Current.Server.MapPath(virtualPath);
        }

        /// <summary>
        /// Gets a set of <see cref="DirectoryCatalog" /> for the specified path and it's immediate child directories.
        /// </summary>
        /// <param name="path">The starting path.</param>
        /// <returns>An <see cref="IEnumerable{DirectoryCatalog}" /> of directory catalogs.</returns>
        protected IEnumerable<DirectoryCatalog> GetDirectoryCatalogs(string path) {
            Throw.IfArgumentNullOrEmpty(path, "path");

            List<DirectoryCatalog> list = new List<DirectoryCatalog>();
            list.Add(new DirectoryCatalog(path));

            list.AddRange(
                Directory.GetDirectories(path).Select(directory => new DirectoryCatalog(directory)));

            return list;
        }

        /// <summary>
        /// Gets the available verbs for the given category.
        /// </summary>
        /// <param name="category">The category of verbs to get.</param>
        /// <returns>An enumerable of verbs.</returns>
        public static IEnumerable<IActionVerb> GetVerbsForCategory(string category) {
            Throw.IfArgumentNullOrEmpty(category, "category");

            return ActionVerbs
                .Where(l => l.Metadata.Category.Equals(category, StringComparison.InvariantCultureIgnoreCase))
                .Select(l => l.Value);
        }
        #endregion
    }
}
